﻿using UnityEngine;
using System.Collections.Generic;

/// <summary>
/// 这个对象池主要管理GameObject
/// 本脚本需要挂在一个GameObject节点上
/// 多实例对象池
/// </summary>
public sealed class MonoObjectPool : MonoBehaviour
{
    private static MonoObjectPool instance;
    /// <summary>
    /// TODO 对象池做成单例的话，全场只能有一个；
	/// 改成可以通过一个静态方法创建一个MonoObjectPool，或者加个MonoObjectPoolManager，用它来管理
    /// </summary>
    public static MonoObjectPool Instance
    {
        get
        {
            if (instance != null)
                return instance;

            instance = FindObjectOfType<MonoObjectPool>();
            if (instance != null)
                return instance;

            GameObject obj = new GameObject(poolName);
            obj.transform.localPosition = Vector3.zero;
            obj.transform.localRotation = Quaternion.identity;
            obj.transform.localScale = Vector3.one;
            instance = obj.AddComponent<MonoObjectPool>();
            return instance;
        }
    }

    /// <summary>
    /// 对象池名
    /// 用于命名脚本挂的GameObject
    /// </summary>
    private static string poolName = "ObjectPool";

    [System.Serializable]
	public class StartupPool
	{
		public int size;
		public GameObject prefab;
	}

    private static List<GameObject> tempList = new List<GameObject>();

    private Dictionary<GameObject, List<GameObject>> pooledObjects = new Dictionary<GameObject, List<GameObject>>();
    private Dictionary<GameObject, GameObject> spawnedObjects = new Dictionary<GameObject, GameObject>();

	public StartupPool[] startupPools;

	private bool startupPoolsCreated;

	void Awake()
	{
		instance = this;
	}

	public static void CreateStartupPools()
	{
		if (!Instance.startupPoolsCreated)
		{
			Instance.startupPoolsCreated = true;
			var pools = Instance.startupPools;
			if (pools != null && pools.Length > 0)
				for (int i = 0; i < pools.Length; ++i)
					CreatePool(pools[i].prefab, pools[i].size);
		}
	}

    #region CreatePool
    public static void CreatePool<T>(T prefab, int initialPoolSize) where T : Component
	{
		CreatePool(prefab.gameObject, initialPoolSize);
	}
    /// <summary>
    /// 创建对象池
    /// 以prefab为模板
    /// </summary>
    /// <param name="prefab">此对象池的GameObject模板</param>
    /// <param name="initialPoolSize">池初始大小</param>
    public static void CreatePool(GameObject prefab, int initialPoolSize)
	{
		if (prefab != null && !Instance.pooledObjects.ContainsKey(prefab))
		{
			var list = new List<GameObject>();
			Instance.pooledObjects.Add(prefab, list);

			if (initialPoolSize > 0)
			{
				bool active = prefab.activeSelf;
				prefab.SetActive(false);
				Transform parent = Instance.transform;
				while (list.Count < initialPoolSize)
				{
					var obj = Instantiate(prefab);
					obj.transform.parent = parent;
					list.Add(obj);
				}
				prefab.SetActive(active);
			}
		}
	}
    #endregion

    #region Spawn
    public static T Spawn<T>(T prefab, Transform parent, Vector3 position, Quaternion rotation) where T : Component
	{
		return Spawn(prefab.gameObject, parent, position, rotation).GetComponent<T>();
	}
	public static T Spawn<T>(T prefab, Vector3 position, Quaternion rotation) where T : Component
	{
		return Spawn(prefab.gameObject, null, position, rotation).GetComponent<T>();
	}
	public static T Spawn<T>(T prefab, Transform parent, Vector3 position) where T : Component
	{
		return Spawn(prefab.gameObject, parent, position, Quaternion.identity).GetComponent<T>();
	}
	public static T Spawn<T>(T prefab, Vector3 position) where T : Component
	{
		return Spawn(prefab.gameObject, null, position, Quaternion.identity).GetComponent<T>();
	}
	public static T Spawn<T>(T prefab, Transform parent) where T : Component
	{
		return Spawn(prefab.gameObject, parent, Vector3.zero, Quaternion.identity).GetComponent<T>();
	}
	public static T Spawn<T>(T prefab) where T : Component
	{
		return Spawn(prefab.gameObject, null, Vector3.zero, Quaternion.identity).GetComponent<T>();
	}

    public static GameObject Spawn(GameObject prefab)
    {
        return Spawn(prefab, null, Vector3.zero, Quaternion.identity);
    }
    public static GameObject Spawn(GameObject prefab, Transform parent)
    {
        return Spawn(prefab, parent, Vector3.zero, Quaternion.identity);
    }
    public static GameObject Spawn(GameObject prefab, Transform parent, Vector3 position)
    {
        return Spawn(prefab, parent, position, Quaternion.identity);
    }
    public static GameObject Spawn(GameObject prefab, Vector3 position)
    {
        return Spawn(prefab, null, position, Quaternion.identity);
    }
    public static GameObject Spawn(GameObject prefab, Vector3 position, Quaternion rotation)
    {
        return Spawn(prefab, null, position, rotation);
    }
    public static GameObject Spawn(GameObject prefab, Transform parent, Vector3 position, Quaternion rotation)
	{
		List<GameObject> list;
		Transform trans;
		GameObject obj;
		if (Instance.pooledObjects.TryGetValue(prefab, out list))
		{
			obj = null;
			if (list.Count > 0)
			{
				while (obj == null && list.Count > 0)
				{
					obj = list[0];
					list.RemoveAt(0);
				}
				if (obj != null)
				{
					trans = obj.transform;
					trans.parent = parent;
					trans.localPosition = position;
					trans.localRotation = rotation;
					obj.SetActive(true);
					Instance.spawnedObjects.Add(obj, prefab);
					return obj;
				}
			}
			obj = Instantiate(prefab);
			trans = obj.transform;
			trans.parent = parent;
			trans.localPosition = position;
			trans.localRotation = rotation;
			Instance.spawnedObjects.Add(obj, prefab);
			return obj;
		}
		else
		{
			obj = Instantiate(prefab);
			trans = obj.GetComponent<Transform>();
			trans.parent = parent;
			trans.localPosition = position;
			trans.localRotation = rotation;
			return obj;
		}
    }
    #endregion

    #region Recycle
    public static void Recycle<T>(T obj) where T : Component
	{
		Recycle(obj.gameObject);
	}
	public static void Recycle(GameObject obj)
	{
		GameObject prefab;
		if (Instance.spawnedObjects.TryGetValue(obj, out prefab))
			Recycle(obj, prefab);
		else
            Destroy(obj);
	}
	static void Recycle(GameObject obj, GameObject prefab)
	{
		Instance.pooledObjects[prefab].Add(obj);
		Instance.spawnedObjects.Remove(obj);
		obj.transform.parent = Instance.transform;
		obj.SetActive(false);
    }
    #endregion

    #region RecycleAll
    public static void RecycleAll<T>(T prefab) where T : Component
	{
		RecycleAll(prefab.gameObject);
	}
	public static void RecycleAll(GameObject prefab)
	{
		foreach (var item in Instance.spawnedObjects)
			if (item.Value == prefab)
				tempList.Add(item.Key);
		for (int i = 0; i < tempList.Count; ++i)
			Recycle(tempList[i]);
		tempList.Clear();
	}
	public static void RecycleAll()
	{
		tempList.AddRange(Instance.spawnedObjects.Keys);
		for (int i = 0; i < tempList.Count; ++i)
			Recycle(tempList[i]);
		tempList.Clear();
    }
    #endregion

    public static bool IsSpawned(GameObject obj)
	{
		return Instance.spawnedObjects.ContainsKey(obj);
    }

    #region CountPooled
    public static int CountPooled<T>(T prefab) where T : Component
	{
		return CountPooled(prefab.gameObject);
	}
	public static int CountPooled(GameObject prefab)
	{
		List<GameObject> list;
		if (Instance.pooledObjects.TryGetValue(prefab, out list))
			return list.Count;
		return 0;
    }
    #endregion

    #region CountSpawned
    public static int CountSpawned<T>(T prefab) where T : Component
	{
		return CountSpawned(prefab.gameObject);
	}
	public static int CountSpawned(GameObject prefab)
	{
		int count = 0 ;
		foreach (var instancePrefab in Instance.spawnedObjects.Values)
			if (prefab == instancePrefab)
				++count;
		return count;
    }
    #endregion

    public static int CountAllPooled()
	{
		int count = 0;
		foreach (var list in Instance.pooledObjects.Values)
			count += list.Count;
		return count;
	}

    #region GetPooled
    public static List<GameObject> GetPooled(GameObject prefab, List<GameObject> list, bool appendList)
	{
		if (list == null)
			list = new List<GameObject>();
		if (!appendList)
			list.Clear();
		List<GameObject> pooled;
		if (Instance.pooledObjects.TryGetValue(prefab, out pooled))
			list.AddRange(pooled);
		return list;
	}
	public static List<T> GetPooled<T>(T prefab, List<T> list, bool appendList) where T : Component
	{
		if (list == null)
			list = new List<T>();
		if (!appendList)
			list.Clear();
		List<GameObject> pooled;
		if (Instance.pooledObjects.TryGetValue(prefab.gameObject, out pooled))
			for (int i = 0; i < pooled.Count; ++i)
				list.Add(pooled[i].GetComponent<T>());
		return list;
    }
    #endregion

    #region GetSpawned
    public static List<GameObject> GetSpawned(GameObject prefab, List<GameObject> list, bool appendList)
	{
		if (list == null)
			list = new List<GameObject>();
		if (!appendList)
			list.Clear();
		foreach (var item in Instance.spawnedObjects)
			if (item.Value == prefab)
				list.Add(item.Key);
		return list;
	}
	public static List<T> GetSpawned<T>(T prefab, List<T> list, bool appendList) where T : Component
	{
		if (list == null)
			list = new List<T>();
		if (!appendList)
			list.Clear();
		var prefabObj = prefab.gameObject;
		foreach (var item in Instance.spawnedObjects)
			if (item.Value == prefabObj)
				list.Add(item.Key.GetComponent<T>());
		return list;
    }
    #endregion

    #region DestroyPooled
    public static void DestroyPooled(GameObject prefab)
	{
		List<GameObject> pooled;
		if (Instance.pooledObjects.TryGetValue(prefab, out pooled))
		{
			for (int i = 0; i < pooled.Count; ++i)
                Destroy(pooled[i]);
			pooled.Clear();
		}
	}
	public static void DestroyPooled<T>(T prefab) where T : Component
	{
		DestroyPooled(prefab.gameObject);
    }
    #endregion

    #region DestroyAll
    public static void DestroyAll(GameObject prefab)
	{
		RecycleAll(prefab);
		DestroyPooled(prefab);
	}
	public static void DestroyAll<T>(T prefab) where T : Component
	{
		DestroyAll(prefab.gameObject);
    }
    #endregion
}

public static class ObjectPoolExtensions
{
    #region CreatePool
    public static void CreatePool<T>(this T prefab, int initialPoolSize = 0) where T : Component
	{
		MonoObjectPool.CreatePool(prefab, initialPoolSize);
	}
	public static void CreatePool(this GameObject prefab, int initialPoolSize = 0)
	{
		MonoObjectPool.CreatePool(prefab, initialPoolSize);
	}
    #endregion

    #region Spawn T : Component
    public static T Spawn<T>(this T prefab) where T : Component
    {
        return MonoObjectPool.Spawn(prefab, null, Vector3.zero, Quaternion.identity);
    }
    public static T Spawn<T>(this T prefab, Transform parent) where T : Component
    {
        return MonoObjectPool.Spawn(prefab, parent, Vector3.zero, Quaternion.identity);
    }
    public static T Spawn<T>(this T prefab, Transform parent, Vector3 position) where T : Component
    {
        return MonoObjectPool.Spawn(prefab, parent, position, Quaternion.identity);
    }
    public static T Spawn<T>(this T prefab, Transform parent, Vector3 position, Quaternion rotation) where T : Component
	{
		return MonoObjectPool.Spawn(prefab, parent, position, rotation);
    }
    public static T Spawn<T>(this T prefab, Vector3 position) where T : Component
    {
        return MonoObjectPool.Spawn(prefab, null, position, Quaternion.identity);
    }
    public static T Spawn<T>(this T prefab, Vector3 position, Quaternion rotation) where T : Component
	{
		return MonoObjectPool.Spawn(prefab, null, position, rotation);
	}
    #endregion

    #region Spawn GameObject
    public static GameObject Spawn(this GameObject prefab)
    {
        return MonoObjectPool.Spawn(prefab, null, Vector3.zero, Quaternion.identity);
    }
    public static GameObject Spawn(this GameObject prefab, Transform parent)
    {
        return MonoObjectPool.Spawn(prefab, parent, Vector3.zero, Quaternion.identity);
    }
    public static GameObject Spawn(this GameObject prefab, Transform parent, Vector3 position)
    {
        return MonoObjectPool.Spawn(prefab, parent, position, Quaternion.identity);
    }
    public static GameObject Spawn(this GameObject prefab, Transform parent, Vector3 position, Quaternion rotation)
	{
		return MonoObjectPool.Spawn(prefab, parent, position, rotation);
    }
    public static GameObject Spawn(this GameObject prefab, Vector3 position)
    {
        return MonoObjectPool.Spawn(prefab, null, position, Quaternion.identity);
    }
    public static GameObject Spawn(this GameObject prefab, Vector3 position, Quaternion rotation)
	{
		return MonoObjectPool.Spawn(prefab, null, position, rotation);
	}
    #endregion

    #region Recycle
    public static void Recycle<T>(this T obj) where T : Component
	{
		MonoObjectPool.Recycle(obj);
	}
	public static void Recycle(this GameObject obj)
	{
		MonoObjectPool.Recycle(obj);
    }
    #endregion

    #region RecycleAll
    public static void RecycleAll<T>(this T prefab) where T : Component
	{
		MonoObjectPool.RecycleAll(prefab);
	}
	public static void RecycleAll(this GameObject prefab)
	{
		MonoObjectPool.RecycleAll(prefab);
    }
    #endregion

    #region CountPooled
    public static int CountPooled<T>(this T prefab) where T : Component
	{
		return MonoObjectPool.CountPooled(prefab);
	}
	public static int CountPooled(this GameObject prefab)
	{
		return MonoObjectPool.CountPooled(prefab);
    }
    #endregion

    #region CountSpawned
    public static int CountSpawned<T>(this T prefab) where T : Component
	{
		return MonoObjectPool.CountSpawned(prefab);
	}
	public static int CountSpawned(this GameObject prefab)
	{
		return MonoObjectPool.CountSpawned(prefab);
    }
    #endregion

    #region GetSpawned
    public static List<GameObject> GetSpawned(this GameObject prefab, List<GameObject> list = null, bool appendList = false)
	{
		return MonoObjectPool.GetSpawned(prefab, list, appendList);
	}

	public static List<T> GetSpawned<T>(this T prefab, List<T> list = null, bool appendList = false) where T : Component
	{
		return MonoObjectPool.GetSpawned(prefab, list, appendList);
	}
    #endregion

    #region GetPooled
    public static List<GameObject> GetPooled(this GameObject prefab, List<GameObject> list = null, bool appendList = false)
	{
		return MonoObjectPool.GetPooled(prefab, list, appendList);
	}
	public static List<T> GetPooled<T>(this T prefab, List<T> list = null, bool appendList = false) where T : Component
	{
		return MonoObjectPool.GetPooled(prefab, list, appendList);
    }
    #endregion

    #region DestroyPooled
    public static void DestroyPooled(this GameObject prefab)
	{
		MonoObjectPool.DestroyPooled(prefab);
	}
	public static void DestroyPooled<T>(this T prefab) where T : Component
	{
		MonoObjectPool.DestroyPooled(prefab.gameObject);
    }
    #endregion

    #region DestroyAll
    public static void DestroyAll(this GameObject prefab)
	{
		MonoObjectPool.DestroyAll(prefab);
	}
	public static void DestroyAll<T>(this T prefab) where T : Component
	{
		MonoObjectPool.DestroyAll(prefab.gameObject);
    }
    #endregion
}
